package com.raft.core.service;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.raft.core.node.NodeId;

/**客户端内部负责处理kv服务端的重定向和选择leader节点的路由器类，按照如下方式选择leader候选服务器：
 * <br/>1、如果没有节点，则抛出没有服务节点错误
 * <br/>2、如果有手动设置的leader节点，则返回手动设置的leader节点id与其他leader节点id的列表
 * <br/>3、如果没有手动设置的leader节点id，则返回任意的节点id列表
 * <br/>候选服务器是一个有顺序的服务器id列表，而不是单台服务器的id。这是因为，如果集群重新发生了选举，手动设置的leader节点id可能是无效的，需要选择其他节点重试。所以，无论是手动设置
 * <br/>的还是自动检测到的leader节点，执行命令时只是候选服务器的第一个leader节点，而不是唯一一个
 * @author zhangsulei
 *
 */
public class ServerRouter {

    private static Logger logger = LoggerFactory.getLogger(ServerRouter.class);
    private final Map<NodeId, Channel> availableServers = new HashMap<>();
    private NodeId leaderId;

    /**发送消息
     * @param payload
     * @return
     */
    public Object send(Object payload) {
    	// 尝试所有可能
        for (NodeId nodeId : getCandidateNodeIds()) {
            try {
            	// 正常返回，设置leader节点
                Object result = doSend(nodeId, payload);
                this.leaderId = nodeId;
                return result;
            } catch (RedirectException e) {
            	// 收到重定向请求，修改leader节点id
                logger.info("not a leader server, redirect to server {}", e.getLeaderId());
                this.leaderId = e.getLeaderId();
                return doSend(e.getLeaderId(), payload);
            } catch (Exception e) {
            	// 连接失败，尝试下一个节点
                logger.info("failed to process with server " + nodeId + ", cause " + e.getMessage());
            }
        }
        throw new NoAvailableServerException("no available server");
    }

    /**获取候选节点id列表
     * @return
     */
    private Collection<NodeId> getCandidateNodeIds() {
    	// 候选为空
        if (availableServers.isEmpty()) {
            throw new NoAvailableServerException("no available server");
        }

        // 已设置
        if (leaderId != null) {
            List<NodeId> nodeIds = new ArrayList<>();
            nodeIds.add(leaderId);
            for (NodeId nodeId : availableServers.keySet()) {
                if (!nodeId.equals(leaderId)) {
                    nodeIds.add(nodeId);
                }
            }
            return nodeIds;
        }

        // 没有设置，任意返回
        return availableServers.keySet();
    }

    private Object doSend(NodeId id, Object payload) {
        Channel channel = this.availableServers.get(id);
        if (channel == null) {
            throw new IllegalStateException("no such channel to server " + id);
        }
        logger.info("send request to server {}", id);
        return channel.send(payload);
    }

    public void add(NodeId id, Channel channel) {
        this.availableServers.put(id, channel);
    }

    public NodeId getLeaderId() {
        return leaderId;
    }

    public void setLeaderId(NodeId leaderId) {
        if (!availableServers.containsKey(leaderId)) {
            throw new IllegalStateException("no such server [" + leaderId + "] in list");
        }
        this.leaderId = leaderId;
    }

}
